
#include <cassert>
#include <iostream>
#include "Interpreter.h"

int Interpreter::CrossJoin(std::vector<std::string>& tables, 
	std::vector<std::string>& whereRPN, std::vector<Tuple>& resultTuples,
	std::string& resultName)
{
	std::cout << "Initiating cross join..." << std::endl;

	// get relation pointers for the tables
	std::vector<Relation*> relationPtrVec;
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		relationPtrVec.push_back(mSchemaManager.getRelation(tables[i]));
		if(relationPtrVec.back() == NULL)
		{
			std::cout << "Table pointer came out NULL for: " << tables[i] << std::endl;
			return -1;
		}
	}

	// get the sizes (in blocks and tuples) of the relations
	std::vector<std::pair<unsigned int, unsigned int> > relationBlockTupleCountVec;
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		relationBlockTupleCountVec.push_back(std::make_pair(
			relationPtrVec[i]->getNumOfBlocks(), relationPtrVec[i]->getNumOfTuples()));
	}


	// create new schema and relation for output, then create tuples
	resultName.assign(GetJoinedName(tables));
	std::cout << "Creating cross-joined table: " << resultName << std::endl;

	std::vector<std::string> resultFieldNames;
	std::vector<enum FIELD_TYPE> resultFieldTypes;
	GetCrossJoinedSchema(tables, resultFieldNames, resultFieldTypes);
	Schema resultSchema(resultFieldNames, resultFieldTypes);

	std::cout << resultSchema << std::endl;

	if(mSchemaManager.createRelation(resultName, resultSchema) == NULL)
		return -1;
	
	Tuple resultTuple = mSchemaManager.getRelation(resultName)->createTuple();

	int totalBlocksNeeded = 0, largestIdx = 0, largestSize = 0;
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		totalBlocksNeeded += relationBlockTupleCountVec[i].first;

		if(relationBlockTupleCountVec[i].first > largestSize)
		{
			largestIdx = i;
			largestSize = relationBlockTupleCountVec[i].first;
		}
	}

	if(totalBlocksNeeded <= mMem.getMemorySize())
	{
		// perform cross join in memory
		std::cout << "In memory operation..." << std::endl;

		// since its all in-memory, join order isn't important
		// just do a cartesian product of all tuples and print to output

		std::vector<std::vector<Tuple> > tuplesOfTables(tables.size());
		for(unsigned int i = 0; i < tables.size(); ++i)
		{
			//
			// we could read them all at memory block 0 coz we know they'll fit in
			// memory, and we're extracting them into this vector for ease of processing
			//
			relationPtrVec[i]->getBlocks(0, 0, relationBlockTupleCountVec[i].first);
			tuplesOfTables[i] = mMem.getTuples(0, relationBlockTupleCountVec[i].first);
		}

		// determine the number of tuples in the result relation (cartesian product)
		unsigned int resultTuplesCount = 1;
		for(unsigned int i = 0; i < tables.size(); ++i)
			resultTuplesCount *= tuplesOfTables[i].size();

		std::cout << "Expecting " << resultTuplesCount 
				<< " tuples in joined table" << std::endl;
		//
		// The below linearization business is done in order to dynamically
		// perform a cartesian product of variable number of sets.
		// This is done by converting the n-dimensional space into a linear
		// 1-D space where each n-D point is mapped to a point in the 1-D space
		// using the index of the point. Its like how the elements of a 2-D array
		// are accessed if the elements are stored in a 1-D array (i*rowCount + j)
		// Here, its tbl0 + weight1*tbl1 + weight2*tbl2 ...
		//

		// determine the weight of the tables in the linearization
		std::vector<unsigned int> weight(tables.size());
		weight[0] = 1;
		for(unsigned int tbl = 1; tbl < tables.size(); ++tbl)
			weight[tbl] = weight[tbl-1]*tuplesOfTables[tbl-1].size();

		int resultFieldIdx = 0;
		for(unsigned int i = 0; i < resultTuplesCount; ++i)
		{
			// generate the joined Tuple here
			// find the tuple to use for each of the tables
			// read sequentially the fields of each tuple and
			// set the field to the result tuple
			// output the tuple

			resultFieldIdx = 0;
			
			// for each table
			for(unsigned int tbl = 0; tbl < tables.size(); ++tbl)
			{
				// use a tuple from the table (linearized tuples with index i)
				Tuple& tempTuple = tuplesOfTables[tbl]
									[(i/weight[tbl]) % tuplesOfTables[tbl].size()];
				
				// grab the fields of the tuple and set to result tuple
				for(int fld = 0; fld < tempTuple.getNumOfFields(); ++fld)
				{
					if(resultFieldTypes[resultFieldIdx] == INT)
						resultTuple.setField(resultFieldIdx, tempTuple.getField(fld).integer);
					else
						resultTuple.setField(resultFieldIdx, *(tempTuple.getField(fld).str));

					++resultFieldIdx;
				}
			}
			assert(resultFieldIdx == resultTuple.getNumOfFields());

			//std::cout << resultTuple << std::endl;
			resultTuples.push_back(resultTuple);
		}
	}
	else
	{
		// assuming no cross join for more than one pass
		if(totalBlocksNeeded - largestSize >= mMem.getMemorySize())
		{
			// the k-1 smallest tables can't fit in M-1
			// one pass won't work
			std::cout << "One pass won't suffice... Exiting!" << std::endl;
			return -1;
		}

		std::cout << "One-pass algorithm..." << std::endl;
		//
		// read the k-1 smallest relations into memory at once
		// read the largest relation block by block
		//
		unsigned int largestTableSize = 0, largestTableIdx = 0;
		for(unsigned int i = 0; i < tables.size(); ++i)
		{
			if(relationBlockTupleCountVec[i].first > largestTableSize)
			{
				largestTableSize = relationBlockTupleCountVec[i].first;
				largestTableIdx = i;
			}
		}

		std::vector<std::vector<Tuple> > tuplesOfTables(tables.size());
		for(unsigned int i = 0; i < tables.size(); ++i)
		{
			if(i == largestTableIdx)
			{
				// size 1
				tuplesOfTables[i].push_back(relationPtrVec[i]->createTuple());
				continue;
			}

			//
			// we could read them all at memory block 0 coz we know they'll fit in
			// memory, except the largest one, and we're extracting them into this 
			// vector for ease of processing
			//
			relationPtrVec[i]->getBlocks(0, 0, relationBlockTupleCountVec[i].first);
			tuplesOfTables[i] = mMem.getTuples(0, relationBlockTupleCountVec[i].first);
		}

		// determine the number of tuples in the result relation (cartesian product)
		unsigned int resultTuplesCount = 1;
		for(unsigned int i = 0; i < tables.size(); ++i)
		{
			resultTuplesCount *= tuplesOfTables[i].size();
		}

		std::cout << "Expecting " << resultTuplesCount 
				<< " times tuples in largest table in joined table" << std::endl;
		//
		// the same linearization as above, except that the table that's to be read
		// block by block gets one tuple worth of weight. The process can then be repeated
		// for each tuple of the largest table
		//

		// determine the weight of the tables in the linearization
		std::vector<unsigned int> weight(tables.size());
		weight[0] = 1;
		for(unsigned int tbl = 1; tbl < tables.size(); ++tbl)
			weight[tbl] = weight[tbl-1]*tuplesOfTables[tbl-1].size();


		int resultFieldIdx = 0;
		std::vector<Tuple> largestTableBlockTuples;
		for(unsigned int blk = 0; blk < relationBlockTupleCountVec[largestTableIdx].first;
				++blk)
		{
			// read one block at a time
			relationPtrVec[largestTableIdx]->getBlocks(blk, 0, 1);
			largestTableBlockTuples = mMem.getTuples(0, 1);

			for(unsigned int tplIdx = 0; tplIdx < largestTableBlockTuples.size(); ++tplIdx)
			{
				// need resultTuplesCount tuples for each tuple of the largest table

				tuplesOfTables[largestTableIdx][0] = largestTableBlockTuples[tplIdx];

				for(unsigned int i = 0; i < resultTuplesCount; ++i)
				{
					// same linearized algorithm as above

					resultFieldIdx = 0;
					
					// for each table
					for(unsigned int tbl = 0; tbl < tables.size(); ++tbl)
					{
						// use a tuple from the table (linearized tuples with index i)
						Tuple& tempTuple = tuplesOfTables[tbl]
											[(i/weight[tbl]) % tuplesOfTables[tbl].size()];
						
						// grab the fields of the tuple and set to result tuple
						for(int fld = 0; fld < tempTuple.getNumOfFields(); ++fld)
						{
							if(resultFieldTypes[resultFieldIdx] == INT)
								resultTuple.setField(resultFieldIdx, 
											tempTuple.getField(fld).integer);
							else
								resultTuple.setField(resultFieldIdx, 
											*(tempTuple.getField(fld).str));

							++resultFieldIdx;
						}
					}
					assert(resultFieldIdx == resultTuple.getNumOfFields());

					//std::cout << resultTuple << std::endl;
					resultTuples.push_back(resultTuple);
				}
			}
		}
	}

	std::cout << "Cross-join done! Wrapping up..." << std::endl;
	//mSchemaManager.deleteRelation(resultName);

	return 0;
}



int Interpreter::GetCrossJoinedSchema(const std::vector<std::string>& tableNames, 
		std::vector<std::string>& resultFieldNames, 
		std::vector<enum FIELD_TYPE>& resultFieldTypes)
{

	char tableNumber[10];

	// Since cross join doesn't involve any projection, we don't really care
	for(unsigned int i = 0; i < tableNames.size(); ++i)
	{
		sprintf(tableNumber, "%d", i);

		std::vector<std::string> fieldNames = 
			mSchemaManager.getRelation(tableNames[i])->getSchema().getFieldNames();

		std::vector<enum FIELD_TYPE> fieldTypes = 
			mSchemaManager.getRelation(tableNames[i])->getSchema().getFieldTypes();

		
		for(unsigned int j = 0; j < fieldNames.size(); ++j)
		{
			resultFieldNames.push_back(fieldNames[j].append(tableNumber));
			resultFieldTypes.push_back(fieldTypes[j]);
		}
	}
	return 0;
}
